﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.IO;

namespace BinaryCode
{
    /// <summary>
    /// Main class of this Project
    /// To use this interpreter simply load the Code with the "Parse" Method and start the Program with "RunCode".
    /// </summary>
    public class CompressedInterpreter:IBaseInterpreter
    {
        /// <summary>
        /// Size of the Memory
        /// </summary>
        private const int Size=100;
        /// <summary>
        /// Integer Memory:
        /// Actions are: GoLeft, GoRight, Add one, Sub one
        /// </summary>
        private int[] memory=new int[Size];
        /// <summary>
        /// Current Position in the Memory.
        /// </summary>
        private int memory_index = Size / 2;

        /// <summary>
        /// All "Commands" of the code.
        /// </summary>
        private string[] code_memory = null;
        /// <summary>
        /// This is used to save the starting points for loops.
        /// </summary>
        private Stack<int> backJump=new Stack<int>();


        /// <summary>
        /// Input for alle Read Commands
        /// </summary>
        private TextReader input;
        /// <summary>
        /// Default output for all Write Commands
        /// </summary>
        private TextWriter output;
        /// <summary>
        /// Output for everything else than Write Commands
        /// </summary>
        private TextWriter error;
        /// <summary>
        /// Creates the Interpreter and resets the memory.
        /// </summary>
        public CompressedInterpreter(TextWriter output=null,TextWriter error=null,TextReader input=null)
        {
            Parallel.For(0, Size, a => memory[a] = 0);
            this.input = (input != null) ? input : Console.In;
            this.output = (output != null) ? output : Console.Out;
            this.error = (error != null) ? error : Console.Error;
        }

        /// <summary>
        /// Fills the internal Code Structure. After calling this Method you can use: RunCode
        /// </summary>
        /// <param name="code">Code as string</param>
        /// <returns>true if this is correct Binary-Code</returns>
        public bool ParseCode(string code)
        {
            code_memory=code.Split(new string[] { " ", "\n", "\t" }, StringSplitOptions.RemoveEmptyEntries);
            return code_memory.AsParallel().All(item => item.All(character => (character == '0' || character == '1')));
        }
        /// <summary>
        /// Runs the Code. This Method will be finished, when the last command of the code is executed.
        /// </summary>
        public void RunCode()
        {
            for (int PC = 0; PC < code_memory.Length; PC++)
            {
                // Processing current Command or ignoring unknown Command (Comments)
                switch (code_memory[PC])
                {
                    case "0": memory_index--; break;
                    case "1": memory_index++; break;
                    case "11": memory[memory_index]++; break;
                    case "10": memory[memory_index]--; break;
                    case "111": output.Write("{0} ", memory[memory_index]); break;
                    case "000": memory[memory_index] = int.Parse(input.ReadLine()); break;
                    case "1010": backJump.Push(PC); break;
                    case "0101": if (memory[memory_index] != 0) PC = backJump.Pop() - 1; break;
                }
            }
        }

        public void SetVerboseOutput(TextWriter output)
        {
            this.error = output;
        }

        public void SetDefaultOutput(TextWriter output)
        {
            this.output = output;
        }

        public void SetDefaultInput(TextReader input)
        {
            this.input = input;
        }
    }
}
